﻿using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Linq;
using AntDesign;
using Blazor.CoffeeTalesApi.Entity;
using Blazor.Localization;
using Blazor.Services.Device;
using Blazor.Services.Update;
using DynamicData;
using Microsoft.Extensions.Localization;
using ReactiveUI;
using HotUpdateInfo = Blazor.Model.Dto.HotUpdateInfo;

namespace Blazor.ViewModels.UpdateSetting;

public class SettingsViewModel : ReactiveObject
{
    private readonly IUpdateService _updateService;
    private readonly IStringLocalizer<UpdateSettingLocalization> _loc;
    private readonly IDeviceService _deviceService;
    private readonly ILogger _logger;
    private readonly ModalService _modalService;

    private Channel Channel { get; }

    private SingleSettingViewModel? _currentSetting;

    public SingleSettingViewModel? CurrentSetting
    {
        get => _currentSetting;
        private set => this.RaiseAndSetIfChanged(ref _currentSetting, value);
    }

    private readonly SourceCache<KeyValuePair<uint, HotUpdateInfo>, uint> _hotUpdateSettingsCache =
        new(i => i.Key);

    public ReadOnlyObservableCollection<uint> NativeVersions { get; }

    #region OAPH

    private readonly ObservableAsPropertyHelper<bool> _isNewingNativeVersion;
    public bool IsNewingNativeVersion => _isNewingNativeVersion.Value;

    private readonly ObservableAsPropertyHelper<bool> _isDeletingNativeVersion;
    public bool IsDeletingNativeVersion => _isDeletingNativeVersion.Value;

    #endregion

    #region Commands
    public ReactiveCommand<uint, Unit> ChangeNativeVersion { get; }

    public ReactiveCommand<Unit, Unit> NewNativeVersion { get; }

    public ReactiveCommand<Unit, Unit> Delete { get; }

    #endregion

    #region Interactions

    public Interaction<Unit, KeyValuePair<uint, HotUpdateInfo>?> NewNativeVersionInteraction { get; } = new();

    public Interaction<Unit, bool> DeleteNativeVersionInteraction { get; } = new();

    #endregion

    public SettingsViewModel(IDictionary<uint, HotUpdateInfo> data, IUpdateService updateService, IStringLocalizer<UpdateSettingLocalization> loc, IDeviceService deviceService, ILogger logger,
        Channel channel, ModalService modalService)
    {
        _updateService = updateService;
        _loc = loc;
        _deviceService = deviceService;
        _logger = logger;
        Channel = channel;
        _modalService = modalService;

        _hotUpdateSettingsCache.Edit(updater =>
        {
            foreach (var pair in data)
            {
                updater.AddOrUpdate(pair);
            }
        });
        
        if (_hotUpdateSettingsCache.Count > 0)
        {
            var (nativeVersion, hotUpdateInfo) = _hotUpdateSettingsCache.Items.MaxBy(pair => pair.Key);
            _currentSetting = new SingleSettingViewModel(hotUpdateInfo, updateService, loc, deviceService, logger, channel, nativeVersion, modalService);
        }

        ChangeNativeVersion = ReactiveCommand.Create<uint>(ChangeNativeVersionImpl, Observable.Return(true));
        NewNativeVersion = ReactiveCommand.CreateFromTask(NewNativeVersionCommandImpl, Observable.Return(true));
        NewNativeVersion.IsExecuting.ToProperty(this, x => x.IsNewingNativeVersion, out _isNewingNativeVersion);
        Delete = ReactiveCommand.CreateFromTask(DeleteImpl,  Observable.Return(true));
        Delete.IsExecuting.ToProperty(this, x => x.IsDeletingNativeVersion, out _isDeletingNativeVersion);

        _hotUpdateSettingsCache.Connect()
            .Transform(kv => kv.Key)
            .Bind(out var keys)
            .Subscribe();
        NativeVersions = keys;

        _hotUpdateSettingsCache.Connect()
            .SelectMany(changes => changes)
            .Where(change => CurrentSetting is not null && change.Key == CurrentSetting.NativeVersion && change.Reason == ChangeReason.Remove)
            .Subscribe(_ => RemoveCurrentSettingHandler());
    }
    
    private void RemoveCurrentSettingHandler()
    {
        var items = _hotUpdateSettingsCache.Items;
        if (items.Any())
        {
            var nativeVersion = items[0].Key;
            var updateInfo = items[0].Value;
            CurrentSetting = new SingleSettingViewModel(updateInfo, _updateService, _loc, _deviceService, _logger, Channel, nativeVersion, _modalService);
        }
        else
        {
            CurrentSetting = null;
        }
    }

    #region Command Implements

    private void ChangeNativeVersionImpl(uint nativeVersion)
    {
        if (!NativeVersions.Contains(nativeVersion))
        {
            throw new ArgumentOutOfRangeException(nameof(nativeVersion));
        }

        var hotUpdateInfo = _hotUpdateSettingsCache.Lookup(nativeVersion);
        if (!hotUpdateInfo.HasValue)
        {
            throw new Exception($"Failed to find hot update info, nativeVersion: {nativeVersion}");
        }
        
        CurrentSetting = new SingleSettingViewModel(hotUpdateInfo.Value.Value, _updateService, _loc, _deviceService, _logger, Channel, nativeVersion, _modalService);
    }

    private async Task NewNativeVersionCommandImpl()
    {
        var pair = await NewNativeVersionInteraction.Handle(new Unit());
        if (!pair.HasValue)
        {
            return;
        }

        var (key, hotUpdateInfo) = pair.Value;
        
        var success = await _updateService.AddHotUpdateSettingAsync(Channel, key, hotUpdateInfo.ResourceVersion, hotUpdateInfo.ResourceUrl);
        if (!success)
        {
            throw new Exception($"Failed to create new hot update info, key: {key}");
        }
        
        _hotUpdateSettingsCache.AddOrUpdate(pair.Value);
    }

    private async Task DeleteImpl()
    {
        var confirmed = await DeleteNativeVersionInteraction.Handle(new Unit());
        if (!confirmed)
        {
            return;
        }

        if (CurrentSetting is null)
        {
            throw new Exception($"Can not delete, current setting is null");
        }

        var success = await _updateService.DeleteHotUpdateSettingAsync(Channel, CurrentSetting.NativeVersion);
        if (!success)
        {
            throw new Exception($"Failed to delete, current setting: {CurrentSetting}");
        }
        
        _hotUpdateSettingsCache.RemoveKey(CurrentSetting.NativeVersion);
    }

    #endregion
}